package com.kl.stream;

import com.kl.model.User;
import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * Stream它并不是一个容器，它只是对容器的功能进行了增强，添加了很多便利的操作,例如查找、过滤、分组、排序等一系列的操作;
 * 并且有串行、并行两种执行模式，并行模式充分的利用了多核处理器的优势，使用fork/join框架进行了任务拆分，同时提高了执行速度;
 * 简而言之，Stream就是提供了一种高效且易于使用的处理数据的方式
 * 注意注意：流的分为三个操作，中间操作是惰性的！！！好好体会
 */
public class StreamStudy {

    List<User> userList;

    /**
     * 流的生命周期
     * 1、起始生成阶段
     * 2、中间操作会逐一获取元素并进行处理，可有可无，所有中间操作都是惰性的，因此，流在管道中流动之前，任何操作都不会产生任何影响
     * 3、终端操作，通常分为 最终的消费 （foreach 之类的）和 归纳 （collect）两类，还有重要的一点就是终端操作启动了流在管道中的流动
     */
    @Before
    public void init() {
        userList = new ArrayList<>();
        User tom = new User("Tom", 18);
        User jerry = new User("Jerry", 19);
        User jack = new User("Jack", 20);
        userList.add(tom);
        userList.add(jerry);
        userList.add(jack);
        userList.add(tom);
    }

    /**
     * 去重
     */
    @Test
    public void test1() {
        // 获取流
        Stream<User> stream = userList.stream();
        // 去重
        Stream<User> distinct = stream.distinct();
        // 输出
        distinct.forEach(System.out::println);
    }

    /**
     * 流只能消费一次，该测试不会通过
     */
    @Test
    public void test2() {
        // 获取流
        Stream<User> stream = userList.stream();
        // 注意：流只能消费一次，forEach属于流的终端操作，会消费掉当前的流
        stream.forEach(System.out::println);
        // 报错：java.lang.IllegalStateException: stream has already been operated upon or closed
        Stream<User> distinct = stream.distinct();
        // 输出
        distinct.forEach(System.out::println);
    }

    /**
     * 断言接口---boolean test(T t)，传入一个参数，返回一个布尔值
     * 过滤年龄小于20的
     */
    @Test
    public void test3() {
        userList.stream()
                .filter((user) -> {
                    return user.getAge() < 20;
                })
                .forEach(System.out::println);
    }

    /**
     * 全匹配---断言接口
     * 所有的用户名都为Tom才为真
     */
    @Test
    public void test4() {
        boolean tom = userList.stream()
                .allMatch((user) -> {
                    return user.getName().equals("Tom");
                });
        System.out.println(tom);
    }

    /**
     * 任意匹配---断言接口
     * 只要有一个用户名为Tom就为真
     */
    @Test
    public void test5() {
        boolean tom = userList.stream()
                .anyMatch((user) -> {
                    return user.getName().equals("Tom");
                });
        System.out.println(tom);
    }

    /**
     * 排序
     * 传入比较器，按照姓名从大到小排序
     */
    @Test
    public void test6() {
        // 方式一：传入比较器，需要实现抽象的比较方法，传入两个参数进行比较，并返回比较结果
        userList.stream().sorted((a, b) -> {
            return b.getAge() - a.getAge();
        }).forEach(System.out::println);

        // 方式二
        userList.stream()
                .sorted(Comparator.comparing(User::getAge)
                        .reversed()).forEach(System.out::println);
    }

    /**
     * 截断
     * 只返回两条数据
     * 如果元素的个数小于maxSize，那就获取所有元素
     */
    @Test
    public void test7() {
        userList.stream().limit(2).forEach(System.out::println);
    }

    /**
     * 跳跃
     * 跳过前n个元素
     * 若流中元素不足 n 个，则返回一个空流
     */
    @Test
    public void test8() {
        userList.stream().skip(2).forEach(System.out::println);
    }

    /**
     * 映射---函数型接口---R apply(T t)，传入一个参数，返回想要的结果
     */
    @Test
    public void test9() {
        // 给所有名字加上欢迎
        Stream<String> stream = userList.stream().map(user -> {
            return "欢迎：" + user.getName();
        });
        stream.forEach(System.out::println);

        // 将所有名字转换为小写
        userList.stream().map(user -> {
            return user.getName().toLowerCase();
        }).forEach(System.out::println);

    }

    /**
     * peek
     */
    @Test
    public void test10() {
        // 不会打印结果，流是惰性的，没有流动之前任何操作都不会对其产生影响
        Stream<User> peek = userList.stream().peek(System.out::println);
        // 将流中的每个元素年龄加1
        userList.stream().peek(user -> {
            user.setAge(user.getAge() + 10);
        }).collect(Collectors.toList())
                .forEach(System.out::println);
        // 打印原来的集合数据，原来的集合数据也被改变了
        System.out.println(userList);
    }

    /**
     * peek ---VS--- map
     * peek 操作 一般用于不想改变流中元素本身的类型或者只想操作元素的内部状态时；
     * 而 map 则用于改变流中元素本身类型，即从元素中派生出另一种类型的操作，这是他们之间的最大区别。
     * 那么 peek 实际中我们会用于哪些场景呢？比如对 Stream<T> 中的 T 的某些属性进行批处理的时候用 peek 操作就比较合适
     * 如果我们要从 Stream<T> 中获取 T 的某个属性的集合时用 map 也就最好不过了
     */
    @Test
    public void test11() {
        // 没有forEach操作打印不会生效
        // 加入forEach后将流开始流动，此时才会输出结果
        userList.stream().peek(user -> {
            System.out.println("welcome " + user.getName());
        }).forEach(System.out::println);

        List<String> username = new ArrayList<>();
        userList.stream().map(user -> {
            username.add(user.getName());
            return username;
        }).forEach(System.out::println);
        /*
         * 输出
         * [Tom]
         * [Tom, Jerry]
         * [Tom, Jerry, Jack]
         * [Tom, Jerry, Jack, Tom]
         */
    }
}
